#pragma once
#include <iostream>
#include <sys/epoll.h>
#include "Socket.hpp"
#include "Log.hpp"
#include "Comm.hpp"
#include "InitAddr.hpp"

using namespace SocketModule;
using namespace LogModule;
const int gdefaultfd = -1;

class EpollServer
{
	static const int revs_num = 64;

public:
	EpollServer(int post)
		: _post(post), _epfd(gdefaultfd), _socket(std::make_unique<SocketModule::TcpSocket>()), _running(false)
	{
	}
	~EpollServer()
	{
		_socket->SocketClose();
		if (_epfd != gdefaultfd)
		{
			close(_epfd);
		}
	}
	void Init()
	{
		_socket->BulidTcpserverMethod(_post);
		// 创建epoll模型
		_epfd = epoll_create(256);
		if (_epfd < 0)
		{
			LOG(LogLevel::ERROR) << "epoll_create error";
			return;
		}
		// 把监听套接字加入到epoll中
		struct epoll_event ev;
		ev.events = EPOLLIN;
		ev.data.fd = _socket->Fd();
		int n = epoll_ctl(_epfd, EPOLL_CTL_ADD, _socket->Fd(), &ev);
		if (n < 0)
		{
			LOG(LogLevel::ERROR) << "epoll_ctl error";
			return;
		}
		LOG(LogLevel::DEBUG) << "epoll_ctl success" << _epfd;
	}
	void Accept()
	{
		Inetaddr addr;
		int clientfd = _socket->SocketAccept(&addr);
		if (clientfd < 0)
		{
			LOG(LogLevel::ERROR) << "accept error";
			return;
		}
		else
		{
			LOG(LogLevel::DEBUG) << "accept success";
			// 我们需要将这个新的newfd添加给epoll，让epoll帮我们进行监管。
			struct epoll_event ev;
			ev.events = EPOLLIN;
			ev.data.fd = clientfd;
			int n = epoll_ctl(_epfd, EPOLL_CTL_ADD, clientfd, &ev);
			if (n < 0)
			{
				LOG(LogLevel::ERROR) << "epoll_ctl error";
				return;
			}
			LOG(LogLevel::DEBUG) << "epoll_ctl success: " << clientfd;
		}
	}
	void Recv(int fd)
	{
		char buffer[1024];
		int n = recv(fd, buffer, sizeof(buffer) - 1, 0);
		if (n < 0)
		{
			LOG(LogLevel::DEBUG) << "客户端读取出错, sockfd: " << fd;
			// 要把客户端踢出epoll
			struct epoll_event ev;
			ev.events = EPOLLIN;
			ev.data.fd = fd;
			int n = epoll_ctl(_epfd,EPOLL_CTL_DEL,fd,&ev);
			if (n < 0)
			{
				LOG(LogLevel::ERROR) << "epoll_ctl error";
			}
			LOG(LogLevel::DEBUG) << "epoll_ctl success: " << fd;
			close(fd);
		}
		else if (n == 0)
		{
			LOG(LogLevel::DEBUG) << "客户端退出, sockfd: " << fd;
			struct epoll_event ev;
			ev.events = EPOLLIN;
			ev.data.fd = fd;
			int n = epoll_ctl(_epfd,EPOLL_CTL_DEL,fd,&ev);
			if (n < 0)
			{
				LOG(LogLevel::ERROR) << "epoll_ctl error";
			}
			LOG(LogLevel::DEBUG) << "epoll_ctl success: " << fd;
			close(fd);
		}
		else
		{
			buffer[n] = 0;
			std::cout << "client# " << buffer << std::endl;
			// 把消息进行回显
			std::string message = "echo# ";
			message += buffer;
			send(fd, message.c_str(), message.size(), 0);
		}
	}
	void Dispatcher(int revs)
	{
		for (int i = 0; i < revs; i++)
		{
			int events = _revs[i].events;
			int fd = _revs[i].data.fd;
			if (fd == _socket->Fd())
			{
				// 监听套接字有事件就绪
				if (events & EPOLLIN)
				{
					Accept();
				}
			}
			else
			{
				if (events & EPOLLIN)
				{
					// 普通读事件
					Recv(fd);
				}
			}
		}
	}
	void Start()
	{
		int timeout = -1;
		_running = true;
		while (true)
		{
			// 不能直接建立Accept,因为Accept是阻塞的,如果没有客户端连接,则会一直阻塞在这里
			// 因为是IO，所以为了减少等待时间，需要使用select，进行多路转接
			// 使用select，可以同时监控多个socket，当某个socket有数据到达时，select会返回，然后进行相应的处理
			// 这样就避免了直接Accept，如果没链接要阻塞住，然后再处理的过程，提高了效率

			int n = epoll_wait(_epfd, _revs, revs_num, timeout);
			if (n < 0)
			{
				perror("epoll_wait error");
				return;
			}
			else if (n == 0)
			{
				// 超时,继续下一次循环
				std::cout << "time out..." << std::endl;
				continue;
			}
			else
			{
				// 有事件就绪了
				// rfds: 内核告诉用户，你关心的rfds中的fd，有哪些已经就绪了！！
				std::cout << "有事件就绪啦..., timeout: " << std::endl;
				Dispatcher(n); // 把已经就绪的sockfd，派发给指定的模块
			}
		}

		_running = false;
	}

private:
	int _post;
	std::unique_ptr<SocketModule::Socket> _socket;
	bool _running;
	int _epfd;
	struct epoll_event _revs[revs_num];
};